package com.zhengjiang.purchase.service.impl;

import com.zhengjiang.purchase.dao.ProductDao;
import com.zhengjiang.purchase.dao.PurchaseRecordDao;
import com.zhengjiang.purchase.model.pojo.ProductPO;
import com.zhengjiang.purchase.model.pojo.PurchaseRecordPO;
import com.zhengjiang.purchase.service.PurchaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
 * @author zhengjiang
 * @version 1.0.0
 * @description 购买产品业务实现层
 * @date 2021/5/22
 **/
@Slf4j
@Service
public class PurchaseServiceImpl implements PurchaseService {

    @Autowired(required = false)
    private ProductDao productDao;

    @Autowired(required = false)
    private PurchaseRecordDao purchaseRecordDao;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Transactional(isolation = Isolation.READ_COMMITTED) // 隔离级别设置为读写提交
    @Override
    public boolean purchase(Long userId, Long productId, int quantity) {

        for (int i = 0; i <3; i++) { //使用限定次数重入的乐观锁，需要将隔离级别设置为读写提交
            ProductPO product = productDao.getProduct1(productId); // （1）不使用锁,高并发场景下会出现超发问题
//          ProductPO product = productDao.getProduct2(productId); // （2）使用数据库悲观锁解决高并发下超发问题,但是存在性能丢失

            // 比较库存和购买数量
            if (product.getStock() >= quantity) {

                int version = product.getVersion();
//            productDao.decreaseProduct1(productId, quantity); //(3) 不使用锁
                int result = productDao.decreaseProduct2(productId, quantity, version); //(4) 使用版本号自增的方式添加乐观锁,避免了超发,但会造成大量请求失败
                if (result == 0) {
                    continue; //通过循环重入解决大量请求失败的问题
                }
                purchaseRecordDao.insertPurchaseRecord(assembleRecord(userId, product, quantity));
                return true;
            }
        }

        return false;
    }

    //首先在redis中执行脚本 hmset product_1 id 1 stock 1000 price 5.00
    private String purchaseScript = "redis.call('sadd',KEYS[1],ARGV[2])\n" //产品编号保存到集合中
            //购买列表
            + "local productPurchaseList = KEYS[2]..ARGV[2]\n"
            //用户编号
            + "local userId = ARGV[1]\n"
            //产品键
            + "local product = 'product_'..ARGV[2]\n"
            //购买数量
            + "local quantity = tonumber(ARGV[3])\n"
            // 当前库存
            + "local stock = tonumber(redis.call('hget',product,'stock'))\n"
            //价格
            + "local price = tonumber(redis.call('hget',product,'price'))\n"
            //购买时间
            + "local purchase_date = ARGV[4]\n"
            //库存不足，返回0
            + " if stock < quantity then return 0 end \n"
            //减库存
            + "stock = stock - quantity \n"
            + "redis.call('hset',product,'stock',tostring(stock))\n"
            //计算价格
            + "local sum = price * quantity\n"
            //合并购买记录数据
            + "local purchaseRecord = userId..','..quantity..','"
            + "..sum..','..price..','..purchase_date\n"
            //将购买记录保存到list里\
            + "redis.call('rpush',productPurchaseList,purchaseRecord)\n"
            //返回成功
            + "return 1 \n"
            ;

    //redis 购买记录集合前缀
    private static final String PURCHASE_PRODUCT_LIST = "purchase_list_";
    //抢购商品集合
    private static final String PRODUCT_SCHEDUAL_SET = "product_schedule_set";
    //32位SHA1编码,第一次执行的时候让redis缓存脚本返回
    private String sha1 = null;

    @Override
    public boolean purchaseRedis(Long userId, Long productId, int quantity) {
        log.info(Thread.currentThread().getName()+" start");
        Long purchaseDate = System.currentTimeMillis();
        Jedis jedis = null;
        try {
             jedis = (Jedis)stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
             if (sha1 == null){
                 sha1 = jedis.scriptLoad(purchaseScript);
             }
             Object res = jedis.evalsha(sha1,2,PRODUCT_SCHEDUAL_SET,PURCHASE_PRODUCT_LIST,
                                        userId+"",productId+"",quantity+"",purchaseDate+"");
             Long result = (Long)res;
             return result == 1;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (jedis!=null && jedis.isConnected()){
                jedis.close();
            }
        }
        return false;
    }


    @Override
    //启用新的独立事务运行,回滚时只会回滚当前方法内部的事务而不会影响全局事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean dealRedisPurchase(List<PurchaseRecordPO> prpList) {
        for (PurchaseRecordPO po : prpList){
            purchaseRecordDao.insertPurchaseRecord(po);
            productDao.decreaseProduct1(po.getProductId(),po.getQuantity());
        }
        return true;
    }

    private PurchaseRecordPO assembleRecord(Long userId, ProductPO product, int quantity) {
        return PurchaseRecordPO.builder()
                .productId(product.getId())
                .userId(userId)
                .price(product.getPrice())
                .quantity(quantity)
                .sum(product.getPrice() * quantity)
                .note("购买日志,时间" + System.currentTimeMillis())
                .build();

    }
}
